#include <string>
#include <cstdlib>
#include <ctime>
#include <iostream>

#include "snake.h"



SnakeBody::SnakeBody()
{
}
//Initialize a snake body
SnakeBody::SnakeBody(int x, int y): mX(x), mY(y){
}
//Return the position of a part of snake's body
int SnakeBody::getX() const
{
    return mX;
}

int SnakeBody::getY() const
{
    return mY;
}
Direction SnakeBody::getDirection() const
{
    return this->bodyDirection;
}
void SnakeBody::setDirection(Direction D)
{
    this->bodyDirection = D;
}
//Overload the == operator for SnakeBody comparison.
bool SnakeBody::operator == (const SnakeBody& snakeBody)
{
    // TODO
    return (mX == snakeBody.mX && mY == snakeBody.mY);
}
//Initialize the snake
Snake::Snake(Map GameMap): mGameMap(GameMap)
{
    this->mMap = GameMap.getMap();
    this->mGameBoardWidth = mMap["colNums"];
    this->mGameBoardHeight = mMap["rowNums"];
    this->initializeSnake();
    this->setRandomSeed();
}
// use current time as seed for random generator
void Snake::setRandomSeed()
{
    std::srand(std::time(nullptr));
}
// Instead of using a random initialization algorithm
// We always put the snake at the center of the game mWindows
void Snake::initializeSnake()
{
    int centerX = this->mGameBoardWidth / 3;
    int centerY = this->mGameBoardHeight / 3;

    for (int i = 0; i < this->mInitialSnakeLength; i ++)
    {
        this->mSnake.push_back(SnakeBody(centerX, centerY + i));
    }
    this->mDirection = Direction::Up;
}
//Check if a given point with axis x, y is on the body of the snake
bool Snake::isPartOfSnake(int x, int y)
{
    // TODO
    for (SnakeBody& mbody : mSnake) {
        if (mbody.getX() == x && mbody.getY() == y) {
            return true;
        }
    }
    return false;
}
 //check if the snake has hit the wall (Assumption: only the head would hit wall.)
bool Snake::hitWall()
{
    for (int i = 0; i < this->mMap["barrierNums"]; i++) {
        if (mSnake[0].getX() == mBarriers[i].first && mSnake[0].getY() == mBarriers[i].second) return true;
    }
    return false;
}

//Check if the snake head is overlapping with its body
bool Snake::hitSelf()
{
    // TODO
    for (int i = 1; i < mSnake.size(); i++) {
        if (mSnake[0] == mSnake[i]) return true;
    }
    return false;
}

//Check if the snake touches the food
int Snake::touchFood()
{
    SnakeBody newHead = this->createNewHead();
    for (int i = 0; i < mFoods.size(); i++) {
        if (newHead.getX() == mFoods[i].first && newHead.getY() == mFoods[i].second) return i;
    }
    return -1;
}
//Make the snake aware of the food
void Snake::senseFood(std::vector<point> Foods)
{
    this->mFoods = Foods;
}
//Return the vector which contain each part of the snake's body
std::vector<SnakeBody>& Snake::getSnake()
{
    return this->mSnake;
}
//Change the direction of the snake
bool Snake::changeDirection(Direction newDirection)
{
    switch (this->mDirection)
    {
        case Direction::Up:
        {
            if (newDirection == Direction::Down) return false;
            break;
        }
        case Direction::Down:
        {
            if (newDirection == Direction::Up) return false;
            break;
        }
        case Direction::Left:
        {
            if (newDirection == Direction::Right) return false;
            break;
        }
        case Direction::Right:
        {
            if (newDirection == Direction::Left) return false;
            break;
        }
    }
    this->mDirection = newDirection;
    
    return true;
}

//Create a new head for the snake according to its current direction
SnakeBody Snake::createNewHead()
{
		/* TODO*/
    int newHeadX = this->mSnake[0].getX();
    int newHeadY = this->mSnake[0].getY(); //Read the position of the current head
    switch (mDirection) { //Read the current heading direction & add the new head according to the direction
        case Direction::Up:
            if (newHeadY == 0) newHeadY = mGameBoardHeight - 1;
            else newHeadY--;
            break;
        case Direction::Down:
            if (newHeadY == mGameBoardHeight - 1) newHeadY = 0 ;
            else newHeadY++;
            break;
        case Direction::Left:
            if (newHeadX == 0) newHeadX = mGameBoardWidth - 1;
            else newHeadX--;
            break;
        case Direction::Right:
            if (newHeadX == mGameBoardWidth - 1) newHeadX = 0;
            else newHeadX++;
            break;
    }
    SnakeBody newHead = SnakeBody(newHeadX, newHeadY);
    newHead.setDirection(mDirection);
    return newHead; //Return the new snake
}

//Move the snake forward
//If eat food, return true, otherwise return false
int Snake::moveFoward()
{
    /* TODO*/
    int touchFood = this->touchFood();
    if (touchFood != -1) {
        SnakeBody newHead = createNewHead();
        this->mSnake.insert(mSnake.begin(), newHead);
        return touchFood; // Eat food
    } else {
        SnakeBody newHead = createNewHead();
        this->mSnake.insert(mSnake.begin(), newHead);
        this->mSnake.pop_back();
        return -1; // Not eat food
    }
}
//Check whether the snake is collided with itself or wall
bool Snake::checkCollision()
{
    if (this->hitWall() || this->hitSelf())
    {
        return true;
    }
    else
    {
        return false;
    }
}

//return the length of snake
int Snake::getLength()
{
    return this->mSnake.size();
}

